style(runner): unify editor shell and panels on the design's type/grid scales - #248
Merged
Conversation
…/grid scales A design-alignment pass over the authoring shell and both drawers, built against Figma 48:6560 (light) / 31:6438 (dark) and the docs assistant: - Typography: one scale in theme.type (base 12/20, small 10/20, row 12/16, label 10/20+tracking) replacing ~50 ad-hoc sizes; sidebar rows raised to 12px by decision, category labels stay 10. - Spacing: 4px grid discipline (8px rhythm) across shell and app pages; geometry-locked exceptions documented inline. - Editor: Fira Code 12/20 (bundled via @fontsource), 16px inset, chromeless gutter, editorBg token over githubDark's #0d1117 (Prec.high — the theme prop outranks plain extensions); Mod-F search panel restyled to shell idiom. - Drawers: full-height, slide-in from the right, docs-assistant chrome; Ask AI transcript/composer restyled after the Docs assistant; Style panel aligned on the 24px inset with Theme Builder's row layout (label + info tooltip left, control right), floating pickers with Common/Pick-color tabs, muted-track segmented controllers, framed menu lists shared with the chat suggestions, dismissible intro notice, and the AI tab on the chat composer. - Classes over inline styles: Drawer registers its stylesheet via installCss; the panels' rules live in panels.css with tokens as CSS variables (--hot-font-* added); the ADR-0026 hover-vs-inline trap disappears with it. - Tooltips for the two toolbar CTAs share one treatment; thin scrollbars shell-wide; theme toggle moved between Download and the avatar; lighter hover token in light mode (0.16 -> 0.10). E2E updated where intent changed: full-height drawers cover the top-bar triggers (switching is keyboard-reachable, tests activate via Enter), the composer is transparent over the drawer surface, the edit box and token rows are located by class/data-token instead of inline-style sniffing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The full-height drawer covered every top-bar control from `100vw - 400px`
rightward: not just the two panel triggers the comment named, but Fork/Save,
Download, the theme toggle and the account menu. Measured with
`elementFromPoint` on an authed bar. That bar is a workspace toolbar rather
than a docs-site header — "restyle the demo, then save it" is the panel's
main flow, and Ctrl+S was the only way left to finish it — so the panel goes
back below the bar. The two e2e tests that had switched to keyboard
activation go back to plain clicks, and `style-panel.spec.ts`'s downgrade
test, which master added after this branch cut and which closes the panel by
clicking its trigger, passes again. That test is the one failing CI.
The rest, in the order they were found:
* The Style tab strip drew its bottom rule in `border` over a `surfaceRaised`
fill. Dark `border` *is* `surfaceRaised` (#222222), so the separator was
invisible — the ADR-0028 trap `panels.css` warns about in its own header.
`controlBorder`, as the drawer's three hairlines already use.
* Both composers ship `rows={1}` under a `max-height: 128px` that nothing
ever set a height against, so a six-line question scrolled inside a 44px
slot with `resize: none` removing the way out. `useAutoGrow` makes the cap
real; keying it on the value shrinks the field back after a send.
* The AI tab swaps the whole footer for its composer, which left the
unlinked-theme warning and the Google-font note with nowhere to mount
(Bugbot). AI styling writes the module through the same `apply`, so an
example the panel cannot wire produced a preview that did not change and no
explanation on the tab. Both notes now render in the tab's own section.
* The AI composer's send button was named "Style", the top-bar trigger's
name, which made any page-scoped `getByRole("button", { name: "Style" })`
ambiguous while that tab was open. "Send", as the chat panel's is.
* `theme.type.label` was added by the type-scale pass and never used, while
its documented call site — `SectionHeader.headerLabel` — stayed
hand-written at the same 10/20/0.8px. Adopted; computed style is unchanged.
* `type.small`'s doc comment pointed at the box description and badge label,
which this branch deliberately raised to 12 for the same reason `row`
records. The comment now describes where `small` is actually used.
* `DocsCascader` kept six `fontSize: 13` declarations, one of them on a line
this branch had already rewritten.
* Only Fira Code 400 was bundled, but the CodeMirror github theme sets
`fontWeight: bold` on several token classes, so those rendered faux-bold.
* `segmentBtn` and `segment` draw the same control and had drifted apart on
`fontWeight`.
Left as a reported finding: the `.hot-info` tooltip is clipped by the drawer
body's own `overflow: auto`, in the same acknowledged bucket as the popovers'
"extends the body's scroll rather than flipping upward". There is no clean
CSS fix — the body has to scroll — and a portaled floating element is new
architecture rather than a review fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The user turn became an accent-filled bubble, but the two inline styles in
`markdown.tsx` that carry their own colour were still written for the muted
surface it replaced.
`codeStyle` sets no `color`, so an inline code chip inherited the bubble's
`accentContrast` (#ffffff) onto its own `surfaceMuted` fill (#f7f7f9 in
light) — white on near-white. `linkStyle` is `accentText`, which in light *is*
`accent` (#1A42E8), so a link on the bubble was the bubble: measured contrast
1.0, i.e. invisible. Dark was better but still under AA at 2.27. A question
that names an option in backticks is the ordinary case, and it came out blank.
`Markdown` takes an `onAccent` flag, threaded through `renderInline` so nested
inlines get it too, and the chat passes it for user turns only. The variants
are white-alpha rather than tokens on purpose: `accent` is mode-invariant
("Handsontable blue — brand"), so what sits on it is mode-invariant too, and
no token means "translucent over the accent". Links underline, because the one
colour guaranteed to read there is the one the surrounding text already uses.
Fenced blocks needed nothing — `preStyle` sets its own `color` on `editorBg`.
The regression test measures a contrast ratio rather than colour inequality:
the light defect was #ffffff on #f7f7f9, two colours that differ and are still
unreadable. Verified to fail with the fix reverted (1.0 light / 2.27 dark) and
pass with it, in both modes. It needs its own `contrastPair` helper — the
existing `contrast(page, selector)` walks up to the first fill with a non-zero
alpha, which would stop at the chip's own translucent white and measure white
against white instead of against the accent showing through it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit db5b9ff. Configure here.
Bugbot on db5b9ff, and it is right. The inline-code half of the assertion took the glyph colour against the bubble's opaque accent fill, which is only the truth while the chip is translucent. The regression it is supposed to catch paints an *opaque* `surfaceMuted` chip and still inherits white: `getComputedStyle` reports white either way, white against the accent clears 4.5, and the defect ships green. The revert check I ran before committing it did fail — but on "link on the bubble" both times. The code half passed with the fix reverted and I read the two failures as the two halves. Now composited: `over(chip fill, bubble fill)` is what the glyphs actually sit on, and the two states differ exactly there. Re-verified with the fix reverted — light fails on inline code at 1.07 (white on #f7f7f9, the original defect) and dark on the link at 2.27 — and passes with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI failed on the width test with 400 against 399.99993896484375. It is the entrance animation this branch added: the drawer keeps a `translateX` — 0 once settled, but a transform all the same — so it sits on a composited layer whose `getBoundingClientRect()` comes back through float matrix maths, and the test was racing the 0.2s transition. Sampled across the animation locally, the rect reads 400.00006103515625 at t+90ms while `offsetWidth` holds 400 at every point; CI landed the same error in the other direction. Layout width is also what "both drawers share one width" means — the CSS contract, not the rendered geometry — so this removes the race rather than waiting the transition out. Only this assertion was exposed: the suite's other `boundingBox()` callers measure untransformed elements or compare relatively. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
qunabu
approved these changes
Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
A design-alignment pass over the runner's authoring shell and both drawers, built against the Sandbox Figma (
48:6560light /31:6438dark) and the docs assistant on handsontable.com/docs.Typography & spacing
theme.type— base 12/20, small 10/20, row 12/16, label 10/20 + 0.8px tracking — replacing ~50 ad-hoc sizes (13/12.5/14/15/17…). Sidebar rows raised to 12px by decision; category labels stay 10.Editor
@fontsource/fira-code), 16px inset, chromeless right-aligned gutter at 40% ink,editorBgtoken over githubDark's#0d1117(Prec.high— thethemeprop outranks plain extensions).Drawers
100vw - 400pxrightward — not only the panel triggers but Fork/Save, Download, the theme toggle and the account menu. That bar is a workspace toolbar, not a docs-site header.Classes over inline styles
Drawerregisters its stylesheet through the newinstallCss; panel rules live insrc/panels.csson--hot-*tokens (font stacks exposed as--hot-font-*). The ADR-0026 inline-background-vs-hover trap disappears where classes took over.E2E
Updated where intent changed: keyboard activation for panel switching under full-height drawers, transparent composer measured against the surface behind it, edit box / token rows located by class and
data-tokeninstead of inline-style sniffing.Test plan
pnpm typecheck(editor-shell, authoring)pnpm e2e(full suite, after merging master) — 215 passed, 144 skipped (gated suites). The 6share-view.spec.tsfailures in a barevite previeware the/d/:idworker routes, which need the API worker; they pass in CI.🤖 Generated with Claude Code
Note
Medium Risk
Broad visual and layout rewrite of the editor, drawers, and Style/Ask AI panels, plus e2e locator changes. No auth or data-path changes, but regressions in contrast, drawer geometry, and panel tests are likely if tokens or classes drift.
Overview
Puts the runner on a single type scale (
theme.type: 12/20 base, 10/20 small, 12/16 row, 10/20 label) and a 4px/8px spacing grid, replacing ad-hoc 13px sizes across the shell, admin, and panels.Editor: bundles Fira Code 400/700, chromeless 12/20 gutter on
editorBg, and restyles CodeMirror’s search panel to shell tokens.Drawers: slide in below the 72px top bar (so toolbar actions stay clickable), share docs-assistant chrome via
installCss. Ask AI gets an accent bubble (readable inline code/links), framed suggestions, and a flush auto-growing composer with ↑ send. Style matches Theme Builder rows (ⓘ tooltips, floating Common/Pick-color pickers), a dismissible intro, brand chip on the primary ramp, and the same composer on the AI tab.Panel rules move to
panels.csson--hot-*tokens. E2E follows:offsetWidthfor drawer width,data-token/classes instead of inline-style sniffing, contrast checks on the user bubble, and “Send” instead of colliding “Style”.Reviewed by Cursor Bugbot for commit 2c463c4. Bugbot is set up for automated code reviews on this repo. Configure here.
Review follow-up (post-merge)
origin/mastermerged in (49 commits; one conflict inicons/ui.tsx, adjacentIconInfoCircle/IconKeyimports). That surfaced the CI failure:style-panel.spec.ts's downgrade test, added to master by 39165b2 after this branch cut, closes the Style panel by clicking its own trigger — which the full-height drawer covered. Fixed by the geometry change above rather than by rewriting the test.Also addressed: the dark tab-strip rule drew
borderonsurfaceRaisedand was invisible (ADR-0028); both composers had amax-heightno code ever grew into, sorows={1}meant a six-line question in a 44px slot; the AI tab's footer swap hid the unlinked-theme and Google-font notes (Bugbot); the AI send button's "Style" name collided with the top-bar trigger's;theme.type.labelwas unused while its own call site stayed hand-written;DocsCascaderkept sixfontSize: 13; only Fira Code 400 was bundled against a theme that setsfontWeight: bold.Known and left: the
.hot-infotooltip is clipped by the drawer body'soverflow: auto, the same bucket as the popovers' documented scroll-extension trade. No clean CSS fix exists — the body has to scroll — and a portaled floating element is more than a review fix.